【IoT】如何实现 ESP32 固件的 OTA 在线升级更新

您所在的位置:网站首页 ESP32 SD卡升级 【IoT】如何实现 ESP32 固件的 OTA 在线升级更新

【IoT】如何实现 ESP32 固件的 OTA 在线升级更新

2024-07-03 21:54| 来源: 网络整理| 查看: 265

1、背景

在实际产品开发过程中,在线升级可以远程解决产品软件开发引入的问题,更好地满足用户需求。

2、OTA 简介

OTA(空中)更新是使用 Wi-Fi 连接而不是串行端口将固件加载到 ESP 模块的过程。

2.1、ESP32 的 OTA 升级有三种方式:

Arduino IDE:主要用于软件开发阶段,实现不接线固件烧写 Web Browser:通过 Web 浏览器手动提供应用程序更新模块 HTTP Server:自动使用http服务器 - 针对产品应用 

在三种升级情况下,必须通过串行端口完成第一个固件上传。 

OTA 进程没有强加的安全性,需要确保开发人员只能从合法/受信任的来源获得更新。更新完成后,模块将重新启动,并执行新的代码。开发人员应确保在模块上运行的应用程序以安全的方式关闭并重新启动。

2.2、保密性 Security

模块必须以无线方式显示,以便通过新的草图进行更新。 这使得模块被强行入侵并加载了其他代码。 为了减少被黑客入侵的可能性,请考虑使用密码保护您的上传,选择某些OTA端口等。

可以提高安全性的 ArduinoOTA 库接口:

void setPort(uint16_t port); void setHostname(const char* hostname); void setPassword(const char* password); void onStart(OTA_CALLBACK(fn)); void onEnd(OTA_CALLBACK(fn)); void onProgress(OTA_CALLBACK_PROGRESS(fn)); void onError(OTA_CALLBACK_ERROR (fn));

已经内置了某些保护功能,不需要开发人员进行任何其他编码。ArduinoOTA和espota.py使用Digest-MD5来验证上传。使用MD5校验和,在ESP端验证传输数据的完整性。

2.2、OTA 升级策略 - 针对 http

ESP32 连接 HTTP 服务器,发送请求 Get 升级固件;每次读取1KB固件数据,写入Flash。

ESP32 SPI Flash 内有与升级相关的(至少)四个分区:OTA data、Factory App、OTA_0、OTA_1。其中 FactoryApp 内存有出厂时的默认固件。

首次进行 OTA 升级时,OTA Demo 向 OTA_0 分区烧录目标固件,并在烧录完成后,更新 OTA data 分区数据并重启。

系统重启时获取 OTA data 分区数据进行计算,决定此后加载 OTA_0 分区的固件执行(而不是默认的 Factory App 分区内的固件),从而实现升级。

同理,若某次升级后 ESP32 已经在执行 OTA_0 内的固件,此时再升级时 OTA Demo 就会向 OTA_1 分区写入目标固件。再次启动后,执行 OTA_1 分区实现升级。以此类推,升级的目标固件始终在 OTA_0、OTA_1 两个分区之间交互烧录,不会影响到出厂时的 Factory App 固件。

 

【IoT】如何实现 ESP32 固件的 OTA 在线升级更新_OTA

3、OTA 实例解析

3,1、Arduino IDE 方案固件更新

 

从 Arduino IDE 无线上传模块适用于以下典型场景: 

在固件开发过程中,通过串行加载更快的替代方案 - 用于更新少量模块,只有模块在与 Arduino IDE 的计算机相同的网络上可用。

参考实例:

#include #include #include #include const char* ssid = ".........."; const char* password = ".........."; void setup() { Serial.begin(115200); Serial.println("Booting"); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("Connection Failed! Rebooting..."); delay(5000); ESP.restart(); } // Port defaults to 3232 // ArduinoOTA.setPort(3232); // Hostname defaults to esp3232-[MAC] // ArduinoOTA.setHostname("myesp32"); // No authentication by default // ArduinoOTA.setPassword("admin"); // Password can be set with it's md5 value as well // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); ArduinoOTA .onStart([]() { String type; if (ArduinoOTA.getCommand() == U_FLASH) type = "sketch"; else // U_SPIFFS type = "filesystem"; // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() Serial.println("Start updating " + type); }) .onEnd([]() { Serial.println("\nEnd"); }) .onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }) .onError([](ota_error_t error) { Serial.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); else if (error == OTA_END_ERROR) Serial.println("End Failed"); }); ArduinoOTA.begin(); Serial.println("Ready"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } void loop() { ArduinoOTA.handle(); }

3,2、Web Browser 方案固件更新

 

该方案使用场景:

直接从 Arduino IDE 加载是不方便或不可能的

用户无法从外部更新服务器公开 OTA 的模块

在设置更新服务器不可行时,将部署后的更新提供给少量模块

参考实例:

#include #include #include #include #include const char* host = "esp32"; const char* ssid = "xxx"; const char* password = "xxxx"; WebServer server(80); /* * Login page */ const char* loginIndex = "" "" "" "" "ESP32 Login Page" "" "" "" "" "" "Username:" "" "" "" "" "" "Password:" "" "" "" "" "" "" "" "" "" "" "function check(form)" "{" "if(form.userid.value=='admin' && form.pwd.value=='admin')" "{" "window.open('/serverIndex')" "}" "else" "{" " alert('Error Password or Username')/*displays error message*/" "}" "}" ""; /* * Server Index Page */ const char* serverIndex = "" "" "" "" "" "progress: 0%" "" "$('form').submit(function(e){" "e.preventDefault();" "var form = $('#upload_form')[0];" "var data = new FormData(form);" " $.ajax({" "url: '/update'," "type: 'POST'," "data: data," "contentType: false," "processData:false," "xhr: function() {" "var xhr = new window.XMLHttpRequest();" "xhr.upload.addEventListener('progress', function(evt) {" "if (evt.lengthComputable) {" "var per = evt.loaded / evt.total;" "$('#prg').html('progress: ' + Math.round(per*100) + '%');" "}" "}, false);" "return xhr;" "}," "success:function(d, s) {" "console.log('success!')" "}," "error: function (a, b, c) {" "}" "});" "});" ""; /* * setup function */ void setup(void) { Serial.begin(115200); // Connect to WiFi network WiFi.begin(ssid, password); Serial.println(""); // Wait for connection while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected to "); Serial.println(ssid); Serial.print("IP address: "); Serial.println(WiFi.localIP()); /*use mdns for host name resolution*/ if (!MDNS.begin(host)) { //http://esp32.local Serial.println("Error setting up MDNS responder!"); while (1) { delay(1000); } } Serial.println("mDNS responder started"); /*return index page which is stored in serverIndex */ server.on("/", HTTP_GET, []() { server.sendHeader("Connection", "close"); server.send(200, "text/html", loginIndex); }); server.on("/serverIndex", HTTP_GET, []() { server.sendHeader("Connection", "close"); server.send(200, "text/html", serverIndex); }); /*handling uploading firmware file */ server.on("/update", HTTP_POST, []() { server.sendHeader("Connection", "close"); server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); ESP.restart(); }, []() { HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { Serial.printf("Update: %s\n", upload.filename.c_str()); if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_WRITE) { /* flashing firmware to ESP*/ if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_END) { if (Update.end(true)) { //true to set the size to the current progress Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); } else { Update.printError(Serial); } } }); server.begin(); } void loop(void) { server.handleClient(); delay(1); }

3.3、HTTP 服务器实现更新

 

ESPhttpUpdate 类可以检查更新并从 HTTP Web 服务器下载二进制文件。可以从网络或 Internet 上的每个 IP 或域名地址下载更新,主要应用于远程服务器更新升级。

参考实例:

/** AWS S3 OTA Update Date: 14th June 2017 Author: Arvind Ravulavaru Purpose: Perform an OTA update from a bin located in Amazon S3 (HTTP Only) Upload: Step 1 : Download the sample bin file from the examples folder Step 2 : Upload it to your Amazon S3 account, in a bucket of your choice Step 3 : Once uploaded, inside S3, select the bin file >> More (button on top of the file list) >> Make Public Step 4 : You S3 URL => http://bucket-name.s3.ap-south-1.amazonaws.com/sketch-name.ino.bin Step 5 : Build the above URL and fire it either in your browser or curl it `curl -I -v http://bucket-name.ap-south-1.amazonaws.com/sketch-name.ino.bin` to validate the same Step 6: Plug in your SSID, Password, S3 Host and Bin file below Build & upload Step 1 : Menu > Sketch > Export Compiled Library. The bin file will be saved in the sketch folder (Menu > Sketch > Show Sketch folder) Step 2 : Upload bin to S3 and continue the above process // Check the bottom of this sketch for sample serial monitor log, during and after successful OTA Update */ #include #include WiFiClient client; // Variables to validate // response from S3 int contentLength = 0; bool isValidContentType = false; // Your SSID and PSWD that the chip needs // to connect to const char* SSID = "YOUR-SSID"; const char* PSWD = "YOUR-SSID-PSWD"; // S3 Bucket Config String host = "bucket-name.s3.ap-south-1.amazonaws.com"; // Host => bucket-name.s3.region.amazonaws.com int port = 80; // Non https. For HTTPS 443. As of today, HTTPS doesn't work. String bin = "/sketch-name.ino.bin"; // bin file name with a slash in front. // Utility to extract header value from headers String getHeaderValue(String header, String headerName) { return header.substring(strlen(headerName.c_str())); } // OTA Logic void execOTA() { Serial.println("Connecting to: " + String(host)); // Connect to S3 if (client.connect(host.c_str(), port)) { // Connection Succeed. // Fecthing the bin Serial.println("Fetching Bin: " + String(bin)); // Get the contents of the bin file client.print(String("GET ") + bin + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n\r\n"); // Check what is being sent // Serial.print(String("GET ") + bin + " HTTP/1.1\r\n" + // "Host: " + host + "\r\n" + // "Cache-Control: no-cache\r\n" + // "Connection: close\r\n\r\n"); unsigned long timeout = millis(); while (client.available() == 0) { if (millis() - timeout > 5000) { Serial.println("Client Timeout !"); client.stop(); return; } } // Once the response is available, // check stuff /* Response Structure HTTP/1.1 200 OK x-amz-id-2: NVKxnU1aIQMmpGKhSwpCBh8y2JPbak18QLIfE+OiUDOos+7UftZKjtCFqrwsGOZRN5Zee0jpTd0= x-amz-request-id: 2D56B47560B764EC Date: Wed, 14 Jun 2017 03:33:59 GMT Last-Modified: Fri, 02 Jun 2017 14:50:11 GMT ETag: "d2afebbaaebc38cd669ce36727152af9" Accept-Ranges: bytes Content-Type: application/octet-stream Content-Length: 357280 Server: AmazonS3 {{BIN FILE CONTENTS}} */ while (client.available()) { // read line till /n String line = client.readStringUntil('\n'); // remove space, to check if the line is end of headers line.trim(); // if the the line is empty, // this is end of headers // break the while and feed the // remaining `client` to the // Update.writeStream(); if (!line.length()) { //headers ended break; // and get the OTA started } // Check if the HTTP Response is 200 // else break and Exit Update if (line.startsWith("HTTP/1.1")) { if (line.indexOf("200") < 0) { Serial.println("Got a non 200 status code from server. Exiting OTA Update."); break; } } // extract headers here // Start with content length if (line.startsWith("Content-Length: ")) { contentLength = atoi((getHeaderValue(line, "Content-Length: ")).c_str()); Serial.println("Got " + String(contentLength) + " bytes from server"); } // Next, the content type if (line.startsWith("Content-Type: ")) { String contentType = getHeaderValue(line, "Content-Type: "); Serial.println("Got " + contentType + " payload."); if (contentType == "application/octet-stream") { isValidContentType = true; } } } } else { // Connect to S3 failed // May be try? // Probably a choppy network? Serial.println("Connection to " + String(host) + " failed. Please check your setup"); // retry?? // execOTA(); } // Check what is the contentLength and if content type is `application/octet-stream` Serial.println("contentLength : " + String(contentLength) + ", isValidContentType : " + String(isValidContentType)); // check contentLength and content type if (contentLength && isValidContentType) { // Check if there is enough to OTA Update bool canBegin = Update.begin(contentLength); // If yes, begin if (canBegin) { Serial.println("Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!"); // No activity would appear on the Serial monitor // So be patient. This may take 2 - 5mins to complete size_t written = Update.writeStream(client); if (written == contentLength) { Serial.println("Written : " + String(written) + " successfully"); } else { Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?" ); // retry?? // execOTA(); } if (Update.end()) { Serial.println("OTA done!"); if (Update.isFinished()) { Serial.println("Update successfully completed. Rebooting."); ESP.restart(); } else { Serial.println("Update not finished? Something went wrong!"); } } else { Serial.println("Error Occurred. Error #: " + String(Update.getError())); } } else { // not enough space to begin OTA // Understand the partitions and // space availability Serial.println("Not enough space to begin OTA"); client.flush(); } } else { Serial.println("There was no content in the response"); client.flush(); } } void setup() { //Begin Serial Serial.begin(115200); delay(10); Serial.println("Connecting to " + String(SSID)); // Connect to provided SSID and PSWD WiFi.begin(SSID, PSWD); // Wait for connection to establish while (WiFi.status() != WL_CONNECTED) { Serial.print("."); // Keep the serial monitor lit! delay(500); } // Connection Succeed Serial.println(""); Serial.println("Connected to " + String(SSID)); // Execute OTA Update execOTA(); } void loop() { // chill } /* * Serial Monitor log for this sketch * * If the OTA succeeded, it would load the preference sketch, with a small modification. i.e. * Print `OTA Update succeeded!! This is an example sketch : Preferences > StartCounter` * And then keeps on restarting every 10 seconds, updating the preferences * * rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0x00 clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:1 load:0x3fff0008,len:8 load:0x3fff0010,len:160 load:0x40078000,len:10632 load:0x40080000,len:252 entry 0x40080034 Connecting to SSID ...... Connected to SSID Connecting to: bucket-name.s3.ap-south-1.amazonaws.com Fetching Bin: /StartCounter.ino.bin Got application/octet-stream payload. Got 357280 bytes from server contentLength : 357280, isValidContentType : 1 Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience! Written : 357280 successfully OTA done! Update successfully completed. Rebooting. ets Jun 8 2016 00:22:57 rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0x00 clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:1 load:0x3fff0008,len:8 load:0x3fff0010,len:160 load:0x40078000,len:10632 load:0x40080000,len:252 entry 0x40080034 OTA Update succeeded!! This is an example sketch : Preferences > StartCounter Current counter value: 1 Restarting in 10 seconds... E (102534) wifi: esp_wifi_stop 802 wifi is not init ets Jun 8 2016 00:22:57 rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0x00 clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:1 load:0x3fff0008,len:8 load:0x3fff0010,len:160 load:0x40078000,len:10632 load:0x40080000,len:252 entry 0x40080034 OTA Update succeeded!! This is an example sketch : Preferences > StartCounter Current counter value: 2 Restarting in 10 seconds... .... * */

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3